import type { RedisClient } from "./client";

export interface KeyValue {
    key: string;
    value: string;
};

export class StrCommand {
    constructor(client: RedisClient) {
        this.client = client;
    }

    private readonly client: RedisClient;

    // return the length of the string after the append operation.
    async append(key: string, value: string): Promise<number> {
        const res = await this.client.exec("APPEND", key, value);
        return res as number;
    }

    // return the value of key after the decrement
    async decr(key: string): Promise<number> {
        const res = await this.client.exec("DECR", key);
        return res as number;
    }

    // return the value of key after the decrement
    async decrBy(key: string, value: number): Promise<number> {
        const res = await this.client.exec("DECRBY", key, value.toFixed(0));
        return res as number;
    }

    // return the value of key, or nil when key does not exist.
    async get(key: string): Promise<null | string> {
        const res = await this.client.exec("GET", key);
        if (res == null) {
            return null;
        }
        return res as string;
    }

    // return the value of key, nil when key does not exist
    async getDel(key: string): Promise<null | string> {
        const res = await this.client.exec("GETDEL", key);
        if (res == null) {
            return null;
        }
        return res as string;
    }

    // return the value of key, nil when key does not exist
    async getEx(key: string, optType: "" | "EX" | "PX" | "EXAT" | "PXAT" | "PERSIST" = "", optValue: number = 0): Promise<null | string> {
        const args = ["GETEX", key];
        if (optType !== "") {
            args.push(optType);
        }
        if (["EX", "PX", "EXAT", "PXAT"].includes(optType)) {
            args.push(optValue.toFixed(0));
        }
        const res = await this.client.exec(...args);
        if (res == null) {
            return null;
        }
        return res as string;
    }

    async getRange(key: string, start: number, end: number): Promise<string> {
        const res = await this.client.exec("GETRANGE", key, start.toFixed(0), end.toFixed(0));
        return res as string;
    }

    // return the value of key after the increment
    async incr(key: string): Promise<number> {
        const res = await this.client.exec("INCR", key);
        return res as number;
    }

    // return the value of key after the increment
    async incrBy(key: string, value: number): Promise<number> {
        const res = await this.client.exec("INCRBY", key, value.toFixed(0));
        return res as number;
    }

    // return the value of key after the increment.
    async incByFloat(key: string, value: number): Promise<string> {
        const res = await this.client.exec("INCRBYFLOAT", key, value.toString());
        return res as string;
    }

    // return list of values at the specified keys.
    async mget(keyList: string[]): Promise<unknown[]> {
        const res = await this.client.exec("MGET", ...keyList);
        return res as unknown[];
    }

    async mset(keyValueList: KeyValue[]): Promise<string> {
        const args = ["MSET"];
        for (const kv of keyValueList) {
            args.push(kv.key, kv.value);
        }
        const res = await this.client.exec(...args);
        return res as string;
    }

    // return 1 for all set,0 for none set
    async msetnx(keyValueList: KeyValue[]): Promise<number> {
        const args = ["MSETNX"];
        for (const kv of keyValueList) {
            args.push(kv.key, kv.value);
        }
        const res = await this.client.exec(...args);
        return res as number;
    }

    async set(key: string, value: string, setType: "" | "NX" | "XX" = "", doGet: boolean = false,
        optType: "" | "EX" | "PX" | "EXAT" | "PXAT" | "KEEPTTL" = "", optValue: number = 0): Promise<null | string> {
        const args = ["SET", key, value];
        if (setType !== "") {
            args.push(setType);
        }
        if (optType !== "") {
            args.push(optType);
        }
        if (["EX", "PX", "EXAT", "PXAT"].includes(optType)) {
            args.push(optValue.toFixed(0));
        }
        const res = await this.client.exec(...args);
        if (res == null) {
            return null;
        }
        return res as string;
    }

    // return the length of the string after it was modified by the command.
    async setRange(key: string, offset: number, value: string): Promise<number> {
        const res = await this.client.exec("SETRANGE", key, offset.toFixed(0), value);
        return res as number;
    }

    // return the length of the string at key, or 0 when key does not exist.
    async strlen(key: string): Promise<number> {
        const res = await this.client.exec("STRLEN", key);
        return res as number;
    }
}